package cn.superlu.s3uploadservice.utils;

import cn.hutool.core.util.IdUtil;
import cn.superlu.s3uploadservice.config.FileProperties;
import cn.superlu.s3uploadservice.constant.FileHttpCodeEnum;
import cn.superlu.s3uploadservice.model.bo.FileUploadInfo;
import cn.superlu.s3uploadservice.model.vo.UploadUrlsVO;
import com.amazonaws.ClientConfiguration;
import com.amazonaws.HttpMethod;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.*;
import com.google.common.collect.HashMultimap;
import io.minio.GetObjectArgs;
import io.minio.GetObjectResponse;
import io.minio.StatObjectArgs;
import io.minio.StatObjectResponse;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.net.URL;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Component
public class AmazonS3Util {

    @Resource
    private FileProperties fileProperties;

    private AmazonS3 amazonS3;

    // spring自动注入会失败
    @PostConstruct
    public void init() {
        ClientConfiguration clientConfiguration = new ClientConfiguration();
        clientConfiguration.setMaxConnections(100);
        AwsClientBuilder.EndpointConfiguration endpointConfiguration = new AwsClientBuilder.EndpointConfiguration(
                fileProperties.getOss().getEndpoint(), fileProperties.getOss().getRegion());
        AWSCredentials awsCredentials = new BasicAWSCredentials(fileProperties.getOss().getAccessKey(),
                fileProperties.getOss().getSecretKey());
        AWSCredentialsProvider awsCredentialsProvider = new AWSStaticCredentialsProvider(awsCredentials);
        this.amazonS3 = AmazonS3ClientBuilder.standard()
                .withEndpointConfiguration(endpointConfiguration)
                .withClientConfiguration(clientConfiguration)
                .withCredentials(awsCredentialsProvider)
                .disableChunkedEncoding()
                .withPathStyleAccessEnabled(true)
                .build();
    }

    /**
     * 获取 Minio 中已经上传的分片文件
     * @param object 文件名称
     * @param uploadId 上传的文件id（由 minio 生成）
     * @return List<Integer>
     */
    @SneakyThrows
    public List<Integer> getListParts(String object, String uploadId) {
        ListPartsRequest listPartsRequest = new ListPartsRequest( fileProperties.getBucketName(), object, uploadId);
        PartListing listParts = amazonS3.listParts(listPartsRequest);
        return listParts.getParts().stream().map(PartSummary::getPartNumber).collect(Collectors.toList());
    }


    /**
     * 单文件签名上传
     * @param object 文件名称（uuid 格式）
     * @return UploadUrlsVO
     */
    public UploadUrlsVO getUploadObjectUrl(String contentType, String object) {
        try {
            log.info("<{}> 开始单文件上传<>", object);
            UploadUrlsVO urlsVO = new UploadUrlsVO();
            List<String> urlList = new ArrayList<>();
            // 主要是针对图片，若需要通过浏览器直接查看，而不是下载，需要指定对应的 content-type
            HashMultimap<String, String> headers = HashMultimap.create();
            if (contentType == null || contentType.equals("")) {
                contentType = "application/octet-stream";
            }
            headers.put("Content-Type", contentType);

            String uploadId = IdUtil.simpleUUID();
            Map<String, String> reqParams = new HashMap<>();
            reqParams.put("uploadId", uploadId);
            //生成预签名的 URL
            GeneratePresignedUrlRequest generatePresignedUrlRequest = new GeneratePresignedUrlRequest(
                    fileProperties.getBucketName(),
                    object, HttpMethod.PUT);
            generatePresignedUrlRequest.addRequestParameter("uploadId", uploadId);
            URL url = amazonS3.generatePresignedUrl(generatePresignedUrlRequest);
            urlList.add(url.toString());
            urlsVO.setUploadId(uploadId).setUrls(urlList);
            return urlsVO;
        } catch (Exception e) {
            log.error("单文件上传失败: {}", e.getMessage());
            throw new RuntimeException(FileHttpCodeEnum.UPLOAD_FILE_FAILED.getMsg());
        }
    }

    /**
     * 初始化分片上传
     * @param fileUploadInfo 前端传入的文件信息
     * @param object object
     * @return UploadUrlsVO
     */
    public UploadUrlsVO initMultiPartUpload(FileUploadInfo fileUploadInfo, String object) {
        Integer chunkCount = fileUploadInfo.getChunkCount();
        String contentType = fileUploadInfo.getContentType();
        String uploadId = fileUploadInfo.getUploadId();

        log.info("文件<{}> - 分片<{}> 初始化分片上传数据 请求头 {}", object, chunkCount, contentType);
        UploadUrlsVO urlsVO = new UploadUrlsVO();
        try {
            // 如果初始化时有 uploadId，说明是断点续传，不能重新生成 uploadId
            if (uploadId == null || uploadId.equals("")) {
                // 第一步，初始化，声明下面将有一个 Multipart Upload
                // 设置文件类型
                ObjectMetadata metadata = new ObjectMetadata();
                metadata.setContentType(contentType);
                InitiateMultipartUploadRequest initRequest = new InitiateMultipartUploadRequest(fileProperties.getBucketName(),
                        object, metadata);
                uploadId = amazonS3.initiateMultipartUpload(initRequest).getUploadId();
                log.info("没有uploadId，生成新的{}",uploadId);
            }
            urlsVO.setUploadId(uploadId);

            List<String> partList = new ArrayList<>();
            for (int i = 1; i <= chunkCount; i++) {
                //生成预签名的 URL
                //设置过期时间，例如 1 小时后
                Date expiration = new Date(System.currentTimeMillis() + 3600 * 1000);
                GeneratePresignedUrlRequest generatePresignedUrlRequest =
                        new GeneratePresignedUrlRequest(fileProperties.getBucketName(), object,HttpMethod.PUT)
                                .withExpiration(expiration);
                generatePresignedUrlRequest.addRequestParameter("uploadId", uploadId);
                generatePresignedUrlRequest.addRequestParameter("partNumber", String.valueOf(i));
                URL url = amazonS3.generatePresignedUrl(generatePresignedUrlRequest);
                partList.add(url.toString());
            }
            log.info("文件初始化分片成功");
            urlsVO.setUrls(partList);
            return urlsVO;
        } catch (Exception e) {
            log.error("初始化分片上传失败: {}", e.getMessage());
            // 返回 文件上传失败
            throw new RuntimeException(FileHttpCodeEnum.UPLOAD_FILE_FAILED.getMsg());
        }
    }

    /**
     * 合并文件
     * @param object object
     * @param uploadId uploadUd
     */
    @SneakyThrows
    public boolean mergeMultipartUpload(String object, String uploadId) {
        log.info("通过 <{}-{}-{}> 合并<分片上传>数据", object, uploadId, fileProperties.getBucketName());
        //构建查询parts条件
        ListPartsRequest listPartsRequest = new ListPartsRequest(
                fileProperties.getBucketName(),
                object,
                uploadId);
        listPartsRequest.setMaxParts(1000);
        listPartsRequest.setPartNumberMarker(0);
        //请求查询
        PartListing partList=amazonS3.listParts(listPartsRequest);
        List<PartSummary> parts = partList.getParts();
        if (parts==null|| parts.isEmpty()) {
            // 已上传分块数量与记录中的数量不对应，不能合并分块
            throw new RuntimeException("分片缺失，请重新上传");
        }
        // 合并分片
        CompleteMultipartUploadRequest compRequest = new CompleteMultipartUploadRequest(
                fileProperties.getBucketName(),
                object,
                uploadId,
                parts.stream().map(partSummary -> new PartETag(partSummary.getPartNumber(), partSummary.getETag())).collect(Collectors.toList()));
        amazonS3.completeMultipartUpload(compRequest);
        return true;
    }


    /**
     * 获取文件内容和元信息，该文件不存在会抛异常
     * @param object object
     * @return StatObjectResponse
     */
    @SneakyThrows
    public S3Object statObject(String object) {
        return amazonS3.getObject(fileProperties.getBucketName(), object);
    }

    @SneakyThrows
    public S3Object getObject(String object, Long offset, Long contentLength) {
        GetObjectRequest request = new GetObjectRequest(fileProperties.getBucketName(), object);
        request.setRange(offset, offset + contentLength - 1);  // 设置偏移量和长度
        return amazonS3.getObject(request);
    }




}
